property
實作有__get__
、__set__
及__delete__
,所以是一種data descriptor
。其具備有簡潔的語法能方便使用,而不用煩惱descriptor
的實作細節。
property
的signature
如下:
property(fget=None, fset=None, fdel=None, doc=None) -> property
可以將其想為一個class
,並接受四個選擇性參數。以下我會以prop
來稱呼由property
建立的property
instance
。
fget
控制instance.prop
時的行為。fset
控制instance.prop = value
時的行為。fdel
控制del instance.prop
時的行為。doc
為prop
的說明文件。如果沒有指定doc
,而prop
裡有fget
時,會將doc
設為fget
中的__doc__
。prop
後,想要新增fget
、fset
或fdel
時,我們不mutate
prop
,而是透過property.getter
、property.setter
或property.deleter
來新建一個prop
返回。# 01
為property
的基本型態。
# 01
class MyClass:
def __init__(self, x):
self.x = x
@property
def x(self):
"""docstrings from fget"""
return self._x
@x.setter
def x(self, value):
self._x = value
@x.deleter
def x(self):
del self._x
if __name__ == '__main__':
my_inst = MyClass(1)
首先我們將property
裝飾於function
x
上,而此function
x
內具有當使用my_inst.x
語法時的行為。此裝飾相當於x = property(x)
:
function
x
作為property
的第一個參數fget
,並返回prop
,且命名為x
。prop
x
內已有fget
及doc
,但還未有fset
及fdel
。接著將x.setter
裝飾於另一個function
x
上,而此function
x
內具有當使用my_inst.x = value
語法時的行為。此裝飾相當於x = x.setter(x)
:
prop
x
的setter
接收這個function
x
。setter
中,將會生成一個新的prop
,以第一步的fget
作為fget
及doc
作為doc
,再加上剛剛接收的function
x
作為fset
。prop
,且命名為x
。現在的prop
x
內已有fget
、fset
及doc
,但還未有fdel
。最後將x.deleter
裝飾於另一個function
x
上,而此function
x
內具有當使用del my_inst.x
語法時的行為。此裝飾相當於x = x.deleter(x)
:
prop
x
的deleter
接收這個function
x
。deleter
中,將會生成一個新的prop
,以上一步的fget
作為fget
、doc
作為doc
及fset
作為fset
,再加上剛剛接收的function
x
作為fdel
。prop
,且命名為x
。現在的prop
x
內已完整擁有fget
、fset
、fdel
及doc
。乍看之下,# 01
只是生成了my_inst
,還沒有任何與prop
x
互動。但仔細看看__init__
,my_inst
已經透過property
這個介面呼叫了prop
x
的fset
來進行self.x = x
(self
就是my_inst
)。
@x.setter
與@x.deleter
裝飾的function
必須與@property
所裝飾的function
名一致,即x
。如果使用不同名字,使用上會變得很困難,且容易出錯(註1
)。
# 01
是基本型態,但視您的程式需要,您可能需要先建立一個prop
,然後再視情況加入fget
、fset
、fdel
或doc
,如# 01a
。
# 01a
class MyClass:
def __init__(self, x):
self.x = x
prop = property()
@prop.getter
def x(self):
"""docstrings from fget"""
return self._x
@x.setter
def x(self, value):
self._x = value
@x.deleter
def x(self):
del self._x
又或者您可以建立好fget
、fset
、fdel
或doc
,再一次生成prop
,如# 01b
。
# 01b
class MyClass:
def __init__(self, x):
self.x = x
def get_x(self):
return self._x
def set_x(self, value):
self._x = value
def del_x(self):
del self._x
x = property(fget=get_x,
fset=set_x,
fdel=del_x,
doc="""docstrings""")
property
適用時機instance
中有一個變數,不想直接被存取,而希望使用者透過給定的接口(getter
、setter
與deleter
)來操作這個變數。由於使用property
來存取變數,與存取一般變數的語法是相同的,所以我們寫code
時,可以不用一開始就決定哪些變數要使用property
,等到code
的中後段再來判斷,而所有的interface
並不需要因此修改。function
。但有些時候,這個變數更像是instance attribute
,並且大多會使用快取的機制時,也是一個適合使用property
的時機。註1:
@x.setter
相當於set_x = x.setter(set_x)
,意思是使用prop
x
的fget
及doc
加上現在的set_x
建立一個新prop
返回,並命名為set_x
。現在MyClass
有兩個prop
:
prop
x
,擁有fget
及doc
。prop
set_x
,擁有fget
、fset
及doc
。@x.deleter
相當於del_x = x.deleter(del_x)
,意思是使用prop
x
的fget
及doc
加上現在的del_x
建立一個新prop
返回,並命名為del_x
。現在MyClass
有三個prop
:
prop
x
,擁有fget
及doc
。prop
set_x
,擁有fget
、fset
及doc
。prop
del_x
,擁有fget
、fdel
及doc
。儘管我們還是可以使用my_inst.x
的語法,但:
my_inst.x = value
必須改成my_inst.set_x = value
。del my_inst.x
必須改成del my_inst.delx
。# 101
class MyClass:
def __init__(self, x):
self.set_x = x # not self.x = x
@property
def x(self):
"""docstrings from fget"""
return self._x
@x.setter
def set_x(self, value):
self._x = value
@x.deleter
def del_x(self):
del self._x
if __name__ == '__main__':
my_inst = MyClass(1)
for name, prop in (('x', MyClass.x),
('set_x', MyClass.set_x),
('del_x', MyClass.del_x)):
print(f'prop {name=}: ')
print(f'type of prop {name} is {type(prop)}')
print(f'fget={prop.fget}')
print(f'fset={prop.fset}')
print(f'fdel={prop.fdel}')
print(f'doc={prop.__doc__}\n')
prop name='x':
type of prop x is <class 'property'>
fget=<function MyClass.x at 0x00000142E5AC4EA0>
fset=None
fdel=None
doc=docstrings from fget
prop name='set_x':
type of prop set_x is <class 'property'>
fget=<function MyClass.x at 0x00000142E5AC4EA0>
fset=<function MyClass.set_x at 0x00000142E5AC4E00>
fdel=None
doc=docstrings from fget
prop name='del_x':
type of prop del_x is <class 'property'>
fget=<function MyClass.x at 0x00000142E5AC4EA0>
fset=None
fdel=<function MyClass.del_x at 0x00000142E5AC6340>
doc=docstrings from fget